home *** CD-ROM | disk | FTP | other *** search
Text File | 2000-06-23 | 57.3 KB | 1,779 lines |
- /*
- File: MPFileCopy.c
-
- Contains: An HFS Plus APIs copying engine, which optionally uses pre-emptive threads.
-
- Written by: Quinn
-
- Copyright: Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.
-
- You may incorporate this Apple sample source code into your program(s) without
- restriction. This Apple sample source code has been provided "AS IS" and the
- responsibility for its operation is yours. You are not permitted to redistribute
- this Apple sample source code as "Apple sample source code" after having made
- changes. If you're going to re-distribute the source, we require that you make
- it clear in the source that the code was descended from Apple sample source
- code, but that you've made changes.
-
- Change History (most recent first):
-
- */
-
- /////////////////////////////////////////////////////////////////
-
- // MIB Setup
-
- #include "MoreSetup.h"
-
- // Mac OS Interfaces
-
- #include <Files.h>
- #include <Multiprocessing.h>
- #include <Gestalt.h>
- #include <Events.h>
- #include <DriverGestalt.h>
- #include <Navigation.h>
- #include <UTCUtils.h>
-
- // Standard C interfaces
-
- #include <stdio.h>
- #include <stddef.h>
- #include <string.h>
-
- // Wacky C interfaces (-:
-
- #include <SIOUX.h>
-
- // Project interfaces
-
- #include "TradDriverLoaderLib.h"
- #include "MoreInterfaceLib.h"
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Testing Flags -----
-
- // Set kNeverUseMPTasks to true when debugging with a debugger
- // that doesn't handle debugging MP tasks.
-
- enum {
- kNeverUseMPTasks = false
- };
-
- // kSpecialCaseClassicForks controls whether the classic
- // (resource and data) forks are copied by special case code
- // (true) or by the general purpose code (false). This allows
- // you to test the general purpose code on current systems
- // which don't support named forks other than the classic ones.
-
- enum {
- kSpecialCaseClassicForks = true
- };
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Tunable Parameters -----
-
- // The following constants control the behaviour of the copy engine.
-
- // BufferSizeForThisVolumeSpeed
-
- enum {
- // kDefaultCopyBufferSize = 2L * 1024 * 1024, // Fast be not very responsive.
- kDefaultCopyBufferSize = 256L * 1024, // Slower, but can still use machine.
- kMaximumCopyBufferSize = 2L * 1024 * 1024,
- kMinimumCopyBufferSize = 1024
- };
-
- // CalculateForksToCopy
-
- enum {
- kExpectedForkCount = 10 // Number of non-classic forks we expect.
- };
-
- // CopyFolder
-
- enum {
- kMaximumFolderDepth = 100, // Arbitrary limit needed for stack allocation, see below.
- kFolderItemsPerBulkCall = 20 // Trades File Manager efficiency versus memory usage, see below.
- };
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Utilities -----
-
- static OSStatus VolumeContainingFSRef(const FSRef *itemRef, FSVolumeRefNum *volume)
- {
- OSStatus err;
- FSCatalogInfo catInfo;
-
- err = FSGetCatalogInfo(itemRef, kFSCatInfoVolume, &catInfo, nil, nil, nil);
- if (err == noErr) {
- *volume = catInfo.volume;
- }
- return err;
- }
-
- static OSStatus IsFileSharingRunning(Boolean *fileSharingIsRunning)
- // Determines whether File Sharing is running. It does this in
- // the most paranoid way, by checking to see whether the process
- // is running. I might be able to just get away with checking
- // whether the volume is being shared, but that would require
- // determining exactly what's causing the problem. I decided
- // to just be paranoid and say "if the File Sharing process is
- // around, we act as if File Sharing is running".
- {
- OSStatus err;
- Boolean done;
- ProcessInfoRec processInfo;
- ProcessSerialNumber psn;
-
- MoreAssertQ(fileSharingIsRunning != nil);
- *fileSharingIsRunning = false;
-
- // Iterate through the process list.
-
- psn.highLongOfPSN = 0;
- psn.lowLongOfPSN = kNoProcess;
- done = false;
- do {
- err = GetNextProcess(&psn);
- if (err == noErr) {
-
- // Get information on this specific process.
- // If the signature matches File Sharing,
- // set *fileSharingIsRunning and leave.
-
- MoreBlockZero(&processInfo, sizeof(processInfo));
- processInfo.processInfoLength = sizeof(processInfo);
- err = GetProcessInformation(&psn, &processInfo);
- if (err == noErr && processInfo.processSignature == 'hhgg') {
- *fileSharingIsRunning = true;
- done = true;
- }
- } else if (err == procNotFound) {
- done = true;
- err = noErr;
- }
- } while (err == noErr & ! done);
-
- return err;
- }
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Transfer Buffer Size -----
-
- static OSStatus GetFileSystemInfo(FSVolumeRefNum vRefNum,
- Boolean *async,
- Boolean *localVolume,
- UInt32 *volumeBytesPerSecond)
- // Returns information about the volume specified by vRefNum.
- // This information allows us to customise our copy engine
- // appropriately. Customisation includes the size of the
- // copy buffer we allocate, whether we test for asynchronicity
- // by calling the low-level driver, and so on.
- {
- OSStatus err;
- GetVolParmsInfoBuffer volParms;
- HParamBlockRec hpb;
- UInt32 bytesPerSecond;
-
- hpb.ioParam.ioNamePtr = nil;
- hpb.ioParam.ioVRefNum = vRefNum;
- hpb.ioParam.ioBuffer = (Ptr) &volParms;
- hpb.ioParam.ioReqCount = sizeof(volParms);
-
- err = PBHGetVolParmsSync(&hpb);
- if (err == noErr) {
-
- // Version 1 of the GetVolParmsInfoBuffer included the vMAttrib
- // field, so we don't really need to test ioActCount. A noErr
- // result indicates that we have the info we need. This is
- // just a paranoia check.
-
- MoreAssertQ(hpb.ioParam.ioActCount >= offsetof(GetVolParmsInfoBuffer, vMVolumeGrade));
-
- if (async != nil) {
- *async = ((volParms.vMAttrib & (1 << bSupportsAsyncRequests)) != 0);
- }
- if (localVolume != nil) {
- *localVolume = (volParms.vMServerAdr == 0);
- }
-
- // On the other hand, vMVolumeGrade was not introduced until
- // version 2 of the GetVolParmsInfoBuffer, so we have to explicitly
- // test whether we got a useful value.
-
- if (volumeBytesPerSecond != nil) {
- bytesPerSecond = 0;
- if (hpb.ioParam.ioActCount >= offsetof(GetVolParmsInfoBuffer, vMForeignPrivID)) {
- if (volParms.vMVolumeGrade <= 0) {
- bytesPerSecond = -volParms.vMVolumeGrade;
- }
- }
- *volumeBytesPerSecond = bytesPerSecond;
- }
- }
- return err;
- }
-
- static ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond)
- // Calculate an appropriate copy buffer size based on the volumes
- // rated speed. Our target is to use a buffer that takes 0.25
- // seconds to fill. This is necessary because the volume might be
- // mounted over a very slow link (like ARA), and if we do a 256 KB
- // read over an ARA link we'll block the File Manager queue for
- // so long that other clients (who might have innocently just
- // called PBGetCatInfoSync) will block for a noticable amount of time.
- //
- // Note that volumeBytesPerSecond might be 0, in which case we assume
- // some default value.
- {
- ByteCount bufferSize;
-
- if (volumeBytesPerSecond == 0) {
- bufferSize = kDefaultCopyBufferSize;
- } else {
- // We want to issue a single read that takes 0.25 of a second,
- // so devide the bytes per second by 4.
- bufferSize = volumeBytesPerSecond / 4;
- }
-
- // Round bufferSize down to 512 byte boundary.
-
- bufferSize &= ~0x01FF;
-
- // Clip to sensible limits.
-
- if (bufferSize < kMinimumCopyBufferSize) {
- bufferSize = kMinimumCopyBufferSize;
- } else if (bufferSize > kMaximumCopyBufferSize) {
- bufferSize = kMaximumCopyBufferSize;
- }
- return bufferSize;
- }
-
- static ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum)
- // This routine calculates the appropriate buffer size for
- // the given vRefNum. It's a simple composition of GetFileSystemInfo
- // BufferSizeForThisVolumeSpeed.
- {
- ByteCount volumeBytesPerSecond;
-
- if ( GetFileSystemInfo(vRefNum, nil, nil, &volumeBytesPerSecond) != noErr ) {
- volumeBytesPerSecond = 0;
- }
- return BufferSizeForThisVolumeSpeed(volumeBytesPerSecond);
- }
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- True Async Tests -----
-
- // The following code is use to determine whether the File Manager should
- // be called asynchronously or not by testing certain low-level functionality.
- // We do this because certain machines, like the Power Macintosh 6500, which
- // should support asynchronous I/O, don't do so in all circumstances. Instead,
- // all local hard disk I/O is done synchronously. This causes us problems,
- // as described below.
- //
- // When you do synchronous I/O from an MP task, the actual I/O is done
- // asynchronously from a 'software interrupt' (technically known as a
- // SWI, pronounced "swee"). This SWI is just like a hardware
- // interrupt in that it can interrupt the current foreground task at any
- // instruction boundary (as long as the interrupt is not masked). It also
- // (currently) executes with interrupts masked.
- //
- // If the underlying hardware does not work asynchronously, executing
- // asynchronous File Manager I/O from this SWI results in a very poor
- // user experience. Specifically, interrupts remain masked for the duration
- // of the I/O, which causes the mouse to jerk horribly.
- //
- // So, our goal is to not do File Manager operations from MP tasks on machines
- // that have this problem. We detect this in a three stage process, implemented
- // by the IsVolumeTrulyAsync routine.
- //
- // 1. Determine whether the volume's file system is asynchronous. Some
- // external file systems blow up if you call them asynchronously.
- //
- // 2. Determine whether the device driver thinks it runs asynchronously. Some
- // older hard disk device drivers just don't support asynchronous operations.
- //
- // 3. If the volume is local, determine whether the driver is really running
- // asynchronously. This is important on machines like the Power Macintosh
- // 6500, where the device driver is asynchronous but the ATA Manager isn't.
- // We do this by issuing a large PBReadAsync and checking to see whether
- // the I/O completion runs before the PBReadAsync returns.
- //
- // If all three of these checks give the green light on both the source and
- // destination volumes, we run the copying engine from an MP task. If they
- // don't, we do everything from system task level code.
- //
- // Note that somes of these checks aren't possible within a Carbon application.
- // This won't be a problem on Mac OS X (because all drivers are async there)
- // but it could be a problem for Carbon applications on traditional Mac OS.
- // I don't have a good solution to that problem at this time.
-
- #if !TARGET_API_MAC_CARBON
-
- // The following code implements part 3 of the true asynchronicity testing
- // algorithm, described above. We
-
- volatile static Boolean gEarlyCompletion;
-
- static IOCompletionUPP gMyCompletionUPP; // points to MyCompletion
-
- static void MyCompletion(ParmBlkPtr paramBlock)
- // The I/O completion routine for the large read request we
- // use for a true test of asynchronicity.
- {
- #pragma unused(paramBlock)
-
- gEarlyCompletion = true;
- }
-
- static OSStatus IsDeviceTrulyAsync(Boolean *async, UInt32 volumeBytesPerSecond, DriverRefNum refNum, SInt16 driveNum)
- // This routine implements part 3 of the true asynchronicity testing
- // algorithm, described above.
- //
- // We make the assumption that the device size is greater
- // than or equal to kMaximumCopyBufferSize, which isn't necessarily
- // true. Should probably revisit that decision at some time.
- {
- OSStatus err;
- Ptr copyBuffer;
- ParamBlockRec pb;
- ByteCount bufferSize;
-
- // Calculate what we would consider to be a "large" request.
-
- bufferSize = BufferSizeForThisVolumeSpeed(volumeBytesPerSecond);
-
- MoreAssertQ(gMyCompletionUPP != nil);
-
- // Create the test buffer.
-
- copyBuffer = NewPtr(bufferSize);
- err = MemError();
- if (err == noErr) {
- gEarlyCompletion = false;
-
- // Issue the request.
-
- pb.ioParam.ioCompletion = gMyCompletionUPP;
- pb.ioParam.ioVRefNum = driveNum;
- pb.ioParam.ioRefNum = refNum;
- pb.ioParam.ioBuffer = copyBuffer;
- pb.ioParam.ioReqCount = bufferSize;
- pb.ioParam.ioPosMode = fsFromStart;
- pb.ioParam.ioPosOffset = 0;
-
- err = PBReadAsync(&pb);
-
- // Did we complete early. Note that this sort of code often
- // needs to use an atomic test and set, but in this case that's
- // not necessary because MyCompletion doesn't ever read
- // gEarlyCompletion.
-
- *async = ! gEarlyCompletion;
-
- // Wait for the I/O to actually complete.
-
- while ( pb.ioParam.ioResult > 0 ) {
- // do nothing
- }
-
- // Clean up.
-
- DisposePtr( copyBuffer );
- MoreAssertQ(MemError() == noErr);
- }
- return err;
- }
-
- static OSStatus IsDeviceAsync(FSVolumeRefNum vRefNum, Boolean *async, DriverRefNum *refNum, SInt16 *driveNum)
- // This routine asks the device driver whether it thinks it's asynchronous
- // using Driver Gestalt.
- {
- OSStatus err;
- HParamBlockRec hpb;
- DriverGestaltParam pb;
- DriverFlags flags;
-
- // First find the driver and drive on which the volume is mounted.
-
- hpb.volumeParam.ioNamePtr = nil;
- hpb.volumeParam.ioVRefNum = vRefNum;
- hpb.volumeParam.ioVolIndex = 0;
- err = PBHGetVInfoSync(&hpb);
-
- // Special case for offline volumes. We're not going anywhere near them.
-
- if (err == noErr) {
- if (hpb.volumeParam.ioVDrvInfo == 0) {
- err = offLinErr;
- }
- }
- if (err == noErr) {
- *refNum = hpb.volumeParam.ioVDRefNum;
- *driveNum = hpb.volumeParam.ioVDrvInfo;
- }
-
- // Then check whether the driver support Driver Gestalt.
-
- if (err == noErr) {
- err = TradGetDriverInformation(*refNum, nil, &flags, nil, nil);
- }
- if (err == noErr) {
- if (TradDriverGestaltIsOn(flags)) {
- // If the driver supports Driver Gestalt, we ask it
- // whether it thinks it supports async.
-
- pb.ioVRefNum = *driveNum;
- pb.ioCRefNum = *refNum;
- pb.csCode = kDriverGestaltCode;
- pb.driverGestaltSelector = kdgSync;
- err = PBStatusSync( (ParamBlockRec *) &pb);
- if (err == noErr) {
- *async = ! (GetDriverGestaltSyncResponse(&pb)->behavesSynchronously);
- }
- } else {
- // If the driver doesn't support Driver Gestalt,
- // we return true. This will cause the caller
- // to try a real test based on I/O. We do this
- // because certain really old drivers, like ".Sony",
- // are async even though they don't support Driver
- // Gestalt.
-
- *async = true;
- }
- }
- return err;
- }
-
- #endif
-
- static OSStatus IsVolumeTrulyAsync(FSVolumeRefNum vRefNum, Boolean *async)
- // This routine sets *async to true if the volume can, within
- // the bounds of our ability to tell, operate asynchronously.
- // The algorithm is described above.
- {
- OSStatus err;
- Boolean localVolume;
- UInt32 volumeBytesPerSecond;
-
- err = GetFileSystemInfo(vRefNum, async, &localVolume, &volumeBytesPerSecond);
- #if TARGET_API_MAC_CARBON
- // To a first approximation, all device drivers are async on Carbon.
- // Actually, we still have the Power Mac 6500 problem on Carbon on
- // traditional Mac OS, but I'm going to ignore that for the moment.
- #else
- {
- DriverRefNum refNum;
- SInt16 driveNum;
-
- if (err == noErr && *async) {
- err = IsDeviceAsync(vRefNum, async, &refNum, &driveNum);
- }
- if (err == noErr && *async && localVolume) {
- err = IsDeviceTrulyAsync(async, volumeBytesPerSecond, refNum, driveNum);
- }
- }
- #endif
- return err;
- }
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Copy Engine -----
-
- struct CopyParams {
- void *copyBuffer;
- ByteCount copyBufferSize;
- UInt32 recursionLimit;
- UInt32 currentDepth;
- Boolean copyingToDropFolder;
- Boolean copyingToLocalVolume;
- };
- typedef struct CopyParams CopyParams;
-
- // The tasks stack size is a combination of two factors:
- //
- // 1. Non-recursive overhead, about 3 KB, which is the
- // sum of the frame sizes of the various non-recursive
- // routines in the maximum depth copy engine call chain.
- //
- // 2. Recursive overhead, about 200 bytes per recursion level,
- // which is the frame size of the CopyFolder routine.
- //
- // With the current numbers, this works out to less than 24 KB,
- // which is not a problem.
-
- enum {
- kCopyFolderFrameSize = 208,
-
- kCopyFileTaskStackSize = (3 * 1024) + (kMaximumFolderDepth * kCopyFolderFrameSize)
- };
-
- static UTCDateTime gMagicBusyCreationDate; // a UTCDateTime equivalent of kMagicBusyCreationDate.
-
- static HFSUniStr255 gDataForkName;
- static HFSUniStr255 gRsrcForkName;
-
- static OSStatus CopyFork(const FSRef *source, SInt64 sourceForkSize, const FSRef *dest,
- SInt16 forkDestRefNum,
- ConstHFSUniStr255Param forkName,
- const CopyParams *params)
- // Copies the fork whose name is forkName from source to dest.
- // A refnum for the destination fork may be supplied in forkDestRefNum.
- // If forkDestRefNum is 0, we must open the destination fork ourselves,
- // otherwise it has been opened for us and we shouldn't close it.
- //
- // Frame Size: 128
- {
- OSStatus err;
- OSStatus err2;
- SInt16 sourceRef;
- SInt16 destRef;
- SInt64 bytesRemaining;
- SInt64 bytesToReadThisTime;
- SInt64 bytesToWriteThisTime;
-
- // If we haven't been passed in a forkDestRefNum (which basically
- // means we're copying into a non-drop folder), create the destination
- // fork. We have to do this regardless of whether sourceForkSize is
- // 0, because we want to preserve empty forks.
-
- if (forkDestRefNum == 0) {
- err = FSCreateFork(dest, forkName->length, forkName->unicode);
-
- // Mac OS 9.0 has a bug (in the AppleShare external file system,
- // I think) [2410374] that causes FSCreateFork to return an errFSForkExists
- // error even though the fork is empty. The following code swallows
- // the error (which is harmless) in that case.
-
- if (err == errFSForkExists && !params->copyingToLocalVolume) {
- err = noErr;
- }
- }
-
- // The remainder of this code only applies if there is actual data
- // in the source fork.
-
- if (err == noErr && sourceForkSize != 0) {
-
- // Prepare for failure.
-
- sourceRef = 0;
- destRef = 0;
-
- // Open up the destination fork, if we're asked to, otherwise
- // just use the passed in forkDestRefNum.
-
- if (forkDestRefNum == 0) {
- err = FSOpenFork(dest, forkName->length, forkName->unicode, fsWrPerm, &destRef);
- } else {
- destRef = forkDestRefNum;
- }
-
- // Open up the source fork.
-
- if (err == noErr) {
- err = FSOpenFork(source, forkName->length, forkName->unicode, fsRdPerm, &sourceRef);
- }
-
- // Here we create space for the entire fork on the destination volume.
- // FSAllocateFork has the right semantics on both traditional Mac OS
- // and Mac OS X. On traditional Mac OS it will allocate space for the
- // file in one hit without any other special action. On Mac OS X,
- // FSAllocateFork is preferable to FSSetForkSize because it prevents
- // the system from zero filling the bytes that were added to the end
- // of the fork (which would be waste becasue we're about to write over
- // those bytes anyway.
-
- if (err == noErr) {
- err = FSAllocateFork(destRef, kFSAllocNoRoundUpMask, fsFromStart, 0, sourceForkSize, nil);
- }
-
- // Copy the file from the source to the destination in chunks of
- // no more than params->copyBufferSize bytes. This is fairly
- // boring code except for the bytesToReadThisTime/bytesToWriteThisTime
- // distinction. On the last chunk, we round bytesToWriteThisTime
- // up to the next 512 byte boundary and then, after we exit the loop,
- // we set the file's EOF back to the real location (if the fork size
- // is not a multiple of 512 bytes).
- //
- // This technique works around a 'bug' in the traditional Mac OS File Manager,
- // where the File Manager will put the last 512-byte block of a large write into
- // the cache (even if we specifically request no caching) if that block is not
- // full. If the block goes into the cache it will eventually have to be
- // flushed, which causes sub-optimal disk performance.
- //
- // This is only done if the destination volume is local. For a network
- // volume, it's better to just write the last bytes directly.
- //
- // This is extreme over-optimisation given the other limits of this
- // sample, but I will hopefully get to the other limits eventually.
-
- bytesRemaining = sourceForkSize;
- while (err == noErr && bytesRemaining != 0) {
- if (bytesRemaining > params->copyBufferSize) {
- bytesToReadThisTime = params->copyBufferSize;
- bytesToWriteThisTime = bytesToReadThisTime;
- } else {
- bytesToReadThisTime = bytesRemaining;
- if (params->copyingToLocalVolume) {
- bytesToWriteThisTime = (bytesRemaining + 0x01FF) & ~0x01FF;
- } else {
- bytesToWriteThisTime = bytesRemaining;
- }
- }
- err = FSReadFork(sourceRef, fsAtMark + noCacheMask, 0, bytesToReadThisTime, params->copyBuffer, nil);
- if (err == noErr) {
- err = FSWriteFork(destRef, fsAtMark + noCacheMask, 0, bytesToWriteThisTime, params->copyBuffer, nil);
- }
- if (err == noErr) {
- bytesRemaining -= bytesToReadThisTime;
- }
- }
- if (err == noErr && (params->copyingToLocalVolume && ((sourceForkSize & 0x01FF) != 0)) ) {
- err = FSSetForkSize(destRef, fsFromStart, sourceForkSize);
- }
-
- // Clean up.
-
- if (sourceRef != 0) {
- err2 = FSCloseFork(sourceRef);
- MoreAssertQ(err2 == noErr);
- if (err == noErr) {
- err = err2;
- }
- }
-
- // Only close destRef if we were asked to open it (ie forkDestRefNum == 0) and
- // we actually managed to open it (ie destRef != 0).
-
- if (forkDestRefNum == 0 && destRef != 0) {
- err2 = FSCloseFork(destRef);
- MoreAssertQ(err2 == noErr);
- if (err == noErr) {
- err = err2;
- }
- }
- }
-
- return err;
- }
-
- // The ForkTracker data structure holds information about a specific fork,
- // specifically the name and the refnum. We use this to build a list of
- // all the forks before we start copying them. We need to do this because,
- // if we're copying into a drop folder, we must open all the forks before
- // we start copying data into any of them.
- //
- // For debug builds, we pad the ForkTracker record out to be a multiple of
- // 8 bytes long. This is because the debug builds use MPGetAllocatedBlockSize
- // in many assertions, but MPGetAllocatedBlockSize only returns the size of
- // the block to an 8 byte resolution. Rather than second guess its results,
- // we just ensure that the ForkTracker element size for debug build is
- // a multiple of 8. Also, we have to use 68K alignment because that's
- // the only guaranteed way to force a specific structure layout.
-
- #pragma options align=mac68k
-
- struct ForkTracker {
- HFSUniStr255 forkName;
- SInt64 forkSize;
- SInt16 forkDestRefNum;
- #if MORE_DEBUG
- SInt16 pad[3];
- #endif
- };
- typedef struct ForkTracker ForkTracker;
- typedef ForkTracker *ForkTrackerPtr;
-
- #pragma options align=reset
-
- static Boolean IdenticalHFSUniStr255(const HFSUniStr255 *lhs, const HFSUniStr255 *rhs)
- // MP Note: I can call memcmp here because it's implement in
- // a library that's statically linked in to our application and
- // I checked the code in that library and it doesn't break the
- // rules for pre-emptive tasks.
- //
- // Frame Size: 80
- {
- return (lhs->length == rhs->length)
- && (memcmp(lhs->unicode, rhs->unicode, lhs->length * sizeof(UniChar)) == 0);
- }
-
- static OSStatus CalculateForksToCopy(const FSRef *source,
- SInt64 *dataForkSize,
- SInt64 *rsrcForkSize,
- ForkTrackerPtr *otherForksParam,
- ItemCount *otherForksCountParam)
- // This routine determines the list of forks that a file has.
- // dataFork is set if the file has a data fork.
- // rsrcFork is set if the file has a resource fork.
- // otherForksParam is set to point to a memory block allocated with
- // MPAllocAligned if the file has forks beyond the resource and data
- // forks. You must free that block with MPFree. otherForksCountParam
- // is set to the count of the number of forks in the otherForksParam
- // array. This count does *not* include the resource and data forks.
- //
- // Frame Size: 656
- {
- OSStatus err;
- Boolean done;
- CatPositionRec iterator;
- HFSUniStr255 thisForkName;
- SInt64 thisForkSize;
- ForkTrackerPtr otherForks;
- ItemCount otherForksCount;
- ItemCount otherForksMemoryBlockCount;
- #if MORE_DEBUG
- Boolean doneDataFork = false;
- Boolean doneRsrcFork = false;
- #endif
-
- MoreAssertQ(source != nil);
- MoreAssertQ(dataForkSize != nil);
- MoreAssertQ(rsrcForkSize != nil);
- MoreAssertQ(otherForksParam != nil);
- MoreAssertQ(*otherForksParam == nil);
- MoreAssertQ(otherForksCountParam != nil);
- MoreAssertQ(*otherForksCountParam == 0);
-
- MoreAssertQ(sizeof(ForkTracker) % 8 == 0); // See note above.
-
- *dataForkSize = 0;
- *rsrcForkSize = 0;
-
- otherForks = nil;
- otherForksCount = 0;
-
- // Iterate through the list of forks, processing each fork name in turn.
-
- iterator.initialize = 0;
- done = false;
- do {
- err = FSIterateForks(source, &iterator, &thisForkName, &thisForkSize, nil);
- if (err == errFSNoMoreItems) {
- err = noErr;
- done = true;
- } else if (err == noErr) {
- if ( kSpecialCaseClassicForks && IdenticalHFSUniStr255(&thisForkName, &gDataForkName) ) {
- #if MORE_DEBUG
- // only one data fork, please
- MoreAssertQ(doneDataFork == false);
- doneDataFork = true;
- #endif
- *dataForkSize = thisForkSize;
- } else if ( kSpecialCaseClassicForks && IdenticalHFSUniStr255(&thisForkName, &gRsrcForkName) ) {
- #if MORE_DEBUG
- // only one resource fork, please
- MoreAssertQ(doneRsrcFork == false);
- doneRsrcFork = true;
- #endif
- *rsrcForkSize = thisForkSize;
- } else {
-
- // We've found a fork other than the resource and data forks.
- // We have to add it to the otherForks array. But the array
- // a) may not have been created yet, and b) may not contain
- // enough elements to hold the new fork.
-
- if (otherForks == nil) {
-
- // The array hasn't been allocated yet, allocate it.
-
- otherForksMemoryBlockCount = kExpectedForkCount;
- otherForks = MPAllocateAligned(sizeof(ForkTracker) * kExpectedForkCount, kMPAllocateDefaultAligned, kNilOptions);
- if (otherForks == nil) {
- err = memFullErr;
- }
- } else {
-
- // If the array doesn't contain enough elements, grow it.
-
- if (otherForksCount == otherForksMemoryBlockCount) {
- ForkTrackerPtr newOtherForks;
-
- newOtherForks = MPAllocateAligned(sizeof(ForkTracker) * (otherForksCount + kExpectedForkCount), kMPAllocateDefaultAligned, kNilOptions);
- if (otherForks == nil) {
- err = memFullErr;
- } else {
- BlockMoveData(otherForks, newOtherForks, sizeof(ForkTracker) * otherForksCount);
- otherForksMemoryBlockCount += kExpectedForkCount;
- MPFree(otherForks);
- otherForks = newOtherForks;
- }
- }
- }
-
- // If we have no error, we know we have space in the otherForks
- // array to place the new fork. Put it there and increment the
- // count of forks.
-
- if (err == noErr) {
- MoreAssertQ(otherForks != nil);
- MoreAssertQ(otherForksCount < otherForksMemoryBlockCount);
- MoreAssertQ(MPGetAllocatedBlockSize(otherForks) == sizeof(ForkTracker) * otherForksMemoryBlockCount);
-
- BlockMoveData(&thisForkName, &otherForks[otherForksCount].forkName, sizeof(thisForkName));
- otherForks[otherForksCount].forkSize = thisForkSize;
- otherForks[otherForksCount].forkDestRefNum = 0;
- otherForksCount += 1;
- }
- }
- }
- } while (err == noErr && ! done);
-
- // Clean up.
-
- if (err != noErr) {
- if (otherForks != nil) {
- MPFree(otherForks);
- otherForks = nil;
- }
- otherForksCount = 0;
- }
- *otherForksParam = otherForks;
- *otherForksCountParam = otherForksCount;
-
- // I had to break up the post-conditions into multiple statements
- // because otherwise they overflow the string length limit of the pre-processor.
- // Jeff Rohl would be proud.
-
- if (err == noErr) {
- // Success post-condition
- MoreAssertQ( ((otherForks == nil) && (otherForksCount == 0))
- || ((otherForks != nil) && (otherForksCount <= otherForksMemoryBlockCount) && (MPGetAllocatedBlockSize(otherForks) == sizeof(ForkTracker) * otherForksMemoryBlockCount)));
- } else {
- // Failure post-condition
- MoreAssertQ((otherForks == nil) && (otherForksCount == 0));
- }
-
- return err;
- }
-
- static OSStatus OpenAllForks(const FSRef *dest, Boolean openDataFork, Boolean openRsrcFork,
- SInt16 *dataRefNum, SInt16 *rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount)
- // Open all the forks of the file. We need to do this when we're copying
- // into a drop folder, where you must open all the forks before starting
- // to write to any of them.
- //
- // IMPORTANT: If it fails, this routine won't close forks that opened successfully.
- // You must call CloseAllForks regardless of whether this routine returns an error.
- //
- // Frame Size: 96
- {
- OSStatus err;
- ItemCount thisForkIndex;
-
- MoreAssertQ(dataRefNum != nil);
- MoreAssertQ(rsrcRefNum != nil);
- MoreAssertQ(*dataRefNum == 0);
- MoreAssertQ(*rsrcRefNum == 0);
- MoreAssertQ((otherForks == nil) || (otherForksCount > 0));
-
- // Open the resource and data forks as a special case.
-
- err = noErr;
- if (openDataFork) {
- // Data fork never needs to be created, so I don't have to FSCreateFork it here.
- err = FSOpenFork(dest, gDataForkName.length, gDataForkName.unicode, fsWrPerm, dataRefNum);
- }
- if (err == noErr && openRsrcFork) {
- // Resource fork never needs to be created, so I don't have to FSCreateFork it here.
- err = FSOpenFork(dest, gRsrcForkName.length, gRsrcForkName.unicode, fsWrPerm, rsrcRefNum);
- }
-
- // Open the other forks.
-
- if (err == noErr) {
- for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
- MoreAssertQ(otherForks[thisForkIndex].forkDestRefNum == 0);
-
- // Create the fork. Swallow afpAccessDenied because this operation
- // causes the external file system compatibility shim in Mac OS 9 to
- // generate a GetCatInfo request to the AppleShare external file system,
- // which in turn causes an AFP GetFileDirParms request on the wire,
- // which the AFP server bounces with afpAccessDenied because the file
- // is in a drop folder. As there's no native support for non-classic
- // forks in current AFP, there's no way I can decide how I should
- // handle this in a non-test case. So I just swallow the error and
- // hope that when native AFP support arrives, the right thing will happen.
-
- err = FSCreateFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode);
- if (err == noErr || err == afpAccessDenied) {
- err = noErr;
- }
-
- // Previously I avoided opening up the fork if the fork if the
- // length was empty, but that confused CopyFork into thinking
- // this wasn't a drop folder copy, so I decided to simply avoid
- // this trivial optimisation. In drop folders, we always open
- // all forks.
-
- if (err == noErr) {
- err = FSOpenFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode, fsWrPerm, &otherForks[thisForkIndex].forkDestRefNum);
- }
- if (err != noErr) {
- break;
- }
- }
- }
- return err;
- }
-
- static OSStatus CloseAllForks(SInt16 dataRefNum, SInt16 rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount)
- // Close all the forks that might have been opened by OpenAllForks.
- //
- // Frame Size: 96
- {
- OSStatus err;
- OSStatus err2;
- ItemCount thisForkIndex;
-
- MoreAssertQ((otherForks == nil) || (otherForksCount > 0));
-
- err = noErr;
- if (dataRefNum != 0) {
- err2 = FSCloseFork(dataRefNum);
- MoreAssertQ(err2 == noErr);
- if (err == noErr) {
- err = err2;
- }
- }
- if (rsrcRefNum != 0) {
- err2 = FSCloseFork(rsrcRefNum);
- MoreAssertQ(err2 == noErr);
- if (err == noErr) {
- err = err2;
- }
- }
- for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
- if (otherForks[thisForkIndex].forkDestRefNum != 0) {
- err2 = FSCloseFork(otherForks[thisForkIndex].forkDestRefNum);
- MoreAssertQ(err2 == noErr);
- if (err == noErr) {
- err = err2;
- }
- }
- }
- return err;
- }
-
- static OSStatus CopyItemsForks(const FSRef *source, const FSRef *dest, CopyParams *params)
- // Copy all the folks of a file or folder from source to dest.
- //
- // Frame Size: 112 (+ 656 in sub-routines)
- {
- OSStatus err;
- OSStatus err2;
- SInt64 dataForkSize;
- SInt64 rsrcForkSize;
- ForkTrackerPtr otherForks;
- ItemCount otherForksCount;
- SInt16 dataRefNum;
- SInt16 rsrcRefNum;
- ItemCount thisForkIndex;
-
- dataRefNum = 0;
- rsrcRefNum = 0;
- otherForks = nil;
- otherForksCount = 0;
-
- // First determine the list of forks that the source has.
-
- err = CalculateForksToCopy(source, &dataForkSize, &rsrcForkSize, &otherForks, &otherForksCount);
- if (err == noErr) {
-
- // If we're copying into a drop folder, open up all of those forks.
- // We have to do this because, once we've starting writing to a fork
- // in a drop folder, we can't open any more forks.
- //
- // We only do this if we're copying into a drop folder in order
- // to conserve FCBs in the more common, non-drop folder case.
-
- if (params->copyingToDropFolder) {
- err = OpenAllForks(dest, (dataForkSize != 0), (rsrcForkSize != 0), &dataRefNum, &rsrcRefNum, otherForks, otherForksCount);
- }
-
- // Copy each fork.
-
- if (err == noErr && (dataForkSize != 0)) {
- err = CopyFork(source, dataForkSize, dest, dataRefNum, &gDataForkName, params);
- }
- if (err == noErr && (rsrcForkSize != 0)) {
- err = CopyFork(source, rsrcForkSize, dest, rsrcRefNum, &gRsrcForkName, params);
- }
- if (err == noErr) {
- for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
- err = CopyFork(source, otherForks[thisForkIndex].forkSize,
- dest, otherForks[thisForkIndex].forkDestRefNum, &otherForks[thisForkIndex].forkName, params);
- if (err != noErr) {
- break;
- }
- }
- }
-
- // Close any forks that might be left open. Note that we have to call
- // this regardless of an error. Also note that this only closes forks
- // that were opened by OpenAllForks. If we're not copying into a drop
- // folder, the forks are opened and closed by CopyFork.
-
- err2 = CloseAllForks(dataRefNum, rsrcRefNum, otherForks, otherForksCount);
- if (err == noErr) {
- err = err2;
- }
- }
-
- // Clean up.
-
- if (otherForks != nil) {
- MPFree(otherForks);
- }
-
- return err;
- }
-
- static volatile UInt32 gState = 0; // Used only during debugging.
-
- static Boolean gFileSharingIsRunning;
-
- static OSStatus CopyFile(const FSRef *source, FSCatalogInfo *sourceCatInfo,
- const FSRef *destDir, ConstHFSUniStr255Param destName,
- CopyParams *params)
- // Copies a file referenced by source to the directory referenced by
- // destDir. destName is the name the file should be given in the
- // destination directory. sourceCatInfo is the catalogue info of
- // the file, which is passed in as an optimisation (we could get it
- // by doing a FSGetCatalogInfo but the caller has already done that
- // so we might as well take advantage of that).
- //
- // Frame Size: 176 (+ 112 + 656 in sub-routines)
- {
- OSStatus err;
- OSStatus junk;
- FSRef dest;
- OSType originalFileType;
- UInt16 originalNodeFlags;
- UTCDateTime originalCreateDate;
-
- // If we're copying to a drop folder, we won't be able to reset this
- // information once the copy is done, so we don't mess it up in
- // the first place. We still clear the locked bit though; items dropped
- // into a drop folder always become unlocked.
-
- if (!params->copyingToDropFolder) {
- // Remember and clear the file's type, so the Finder doesn't
- // look at the file until we're done.
-
- originalFileType = ((FInfo *) &sourceCatInfo->finderInfo)->fdType;
- ((FInfo *) &sourceCatInfo->finderInfo)->fdType = kFirstMagicBusyFiletype;
-
- // Set the file's creation date to kMagicBusyCreationDate,
- // remembering the old value for restoration later.
-
- originalCreateDate = sourceCatInfo->createDate;
- sourceCatInfo->createDate = gMagicBusyCreationDate;
-
- // Remember and clear the file's locked status, so that we can
- // actually write the forks we're about to create.
-
- originalNodeFlags = sourceCatInfo->nodeFlags;
- }
- sourceCatInfo->nodeFlags &= ~kFSNodeLockedMask;
-
- // Create the file in destDir.
-
- // In Mac OS 9.0, File Sharing messes up calls to FSCreateFileUnicode such that,
- // if you pass in sourceCatInfo, the file will be created with a blank file
- // type and creator code. [2397324] I work around this by, if File
- // Sharing is running, splitting the create into two parts: first create the file,
- // then set the catalogue info. This defeats the purpose of the unified call (ie
- // the operation isn't atomic) but them's the breaks. Also, I found that if you
- // set the catalogue info directly after creating the file, you trigger another
- // aspect of this File Sharing problem and you end up with weird zombie files that
- // are half locked and half unlocked. To solve this I don't set any catalogue
- // info here and set all the info at the end of the routine, where normally I just
- // restore the modification dates etc.
- //
- // This should be OK because the FSCreateFileUnicode creates a file with a zero
- // fdType, which the Finder will ignore.
- //
- // I'm scared now.
-
- if ( ! gFileSharingIsRunning || params->copyingToDropFolder) {
- // File sharing not running, let's do this the right way.
- err = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoSettableInfo, sourceCatInfo, &dest, nil);
- } else {
- // File sharing is running, use the workaround.
- err = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoNone, nil, &dest, nil);
- }
-
- // Iterate through the forks, copying each fork.
-
- if (err == noErr) {
- err = CopyItemsForks(source, &dest, params);
-
- // Restore the original file type, creation and modification dates, and
- // locked status. This is one of the places where we need to handle drop
- // folders as a special case because this FSSetCatalogInfo will fail for
- // an item in a drop folder, so we don't even attempt it.
- //
- // Also, if File Sharing is running we use this point to set up all
- // the catalogue info.
-
- if (err == noErr && !params->copyingToDropFolder) {
- ((FInfo *) &sourceCatInfo->finderInfo)->fdType = originalFileType;
- sourceCatInfo->createDate = originalCreateDate;
- sourceCatInfo->nodeFlags = originalNodeFlags;
- if (gFileSharingIsRunning) {
- err = FSSetCatalogInfo(&dest, kFSCatInfoCreateDate
- | kFSCatInfoAttrMod
- | kFSCatInfoContentMod
- | kFSCatInfoFinderInfo
- | kFSCatInfoNodeFlags, sourceCatInfo);
- } else {
- err = FSSetCatalogInfo(&dest, kFSCatInfoSettableInfo, sourceCatInfo);
- }
- }
-
- // If we created the file and the copy failed, try to clean up by
- // deleting the file we created. We do this because, while it's
- // possible for the copy to fail halfway through and we don't really
- // clean up that well, we *really* don't wan't any half-created
- // files being left around.
- //
- // Note that there are cases where the assert can fire which are not
- // errors (for example, if the destination is in a drop folder) but
- // I'll leave it in anyway because I'm interested in discovering those
- // cases. Note that, if this fires and we're running MP, current versions
- // of MacsBug won't catch the exception and the MP task will terminate
- // with an kMPTaskAbortedErr error.
-
- if (err != noErr) {
- junk = FSDeleteObject(&dest);
- MoreAssertQ(junk == noErr);
- }
- }
-
- return err;
- }
-
- static OSStatus CopyFolder(const FSRef *source, FSCatalogInfo *sourceCatInfo,
- const FSRef *destDir, ConstHFSUniStr255Param destName,
- CopyParams *params)
- // Copies a folder referenced by source to the directory referenced by
- // destDir. destName is the name the folder should be given in the
- // destination directory. sourceCatInfo is the catalogue info of
- // the folder, which is passed in as an optimisation (we could get it
- // by doing a FSGetCatalogInfo but the caller has already done that
- // so we might as well take advantage of that).
- //
- // IMPORTANT: This routine calls itself recursively, so adding local
- // variables can significantly effect the stack usage of the operation.
- // If you add local variables, remember to update the constant
- // kCopyFolderFrameSize to be greater than or equal to the real frame size.
- //
- // Frame Size: 208 (see kCopyFolderFrameSize) (+ 176 + 112 + 656 in sub-routines)
- {
- OSStatus err; // stack, 4 bytes
- OSStatus junk; // stack, 4 bytes
- FSRef newDir; // stack, 80 bytes
- FSIterator iterator; // stack, 4 bytes
- ItemCount foundItems; // stack, 4 bytes
- FSCatalogInfo *foundCatInfos; // heap, 144 bytes per element
- FSRef *foundFSRefs; // heap, 80 bytes per element
- HFSUniStr255 *foundNames; // heap, 512 bytes per element
- Boolean done; // stack, 4 bytes
- ItemCount i; // stack, 4 bytes
- UTCDateTime originalCreateDate; // stack, 8 bytes
-
- // Check that we haven't run off the bottom of our stack.
-
- err = noErr;
- params->currentDepth += 1;
- if (params->currentDepth > params->recursionLimit) {
- err = -1;
- }
-
- // Allocate memory for temporary buffers.
- //
- // 736 bytes per element per kFolderItemsPerBulkCall (currently 20) per folder nesting depth
-
- if (err == noErr) {
- foundCatInfos = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(FSCatalogInfo),
- kMPAllocateDefaultAligned, kNilOptions);
- foundFSRefs = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(FSRef),
- kMPAllocateDefaultAligned, kNilOptions);
- foundNames = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(HFSUniStr255),
- kMPAllocateDefaultAligned, kNilOptions);
- if (foundCatInfos == nil || foundFSRefs == nil || foundNames == nil) {
- err = memFullErr;
- }
- }
-
- // Create the destination directory.
-
- if (err == noErr) {
-
- // Set the folder's creation date to kMagicBusyCreationDate
- // so that the Finder doesn't mess with the folder while
- // it's copying. We remember the old value for restoration
- // later. We only do this if we're not copying to a drop
- // folder, because if we are copying to a drop folder we don't
- // have the opportunity to reset the information at the end of
- // this routine.
-
- if (!params->copyingToDropFolder) {
- originalCreateDate = sourceCatInfo->createDate;
- sourceCatInfo->createDate = gMagicBusyCreationDate;
- }
-
- err = FSCreateDirectoryUnicode(destDir, destName->length, destName->unicode,
- kFSCatInfoSettableInfo,
- sourceCatInfo,
- &newDir, nil, nil);
- }
-
- // With the new APIs, folders can have forks as well as files. Before
- // we start copying items in the folder, we
-
- if (err == noErr) {
- err = CopyItemsForks(source, &newDir, params);
- }
-
- // Iterate through the source code directory, copying each item to the newly
- // created directory.
-
- if (err == noErr) {
- err = FSOpenIterator(source, kFSIterateFlat, &iterator);
- if (err == noErr) {
-
- // Repeatedly call GetCatalogInfoBulk to get the contents
- // of the directory, and copy the contents.
- //
- // Note that we call GetCatalogInfoBulk with kFSCatInfoSettableInfo,
- // not kFSCatInfoGettableInfo, because there's no point getting
- // information that we can't set back when we're creating the copied item.
-
- done = false;
- do {
- err = FSGetCatalogInfoBulk(iterator, kFolderItemsPerBulkCall,
- &foundItems, nil,
- kFSCatInfoSettableInfo,
- foundCatInfos, foundFSRefs, nil, foundNames);
- if (err == errFSNoMoreItems) {
- // We ran off the end of the directory. Record that we're
- // done, but set err to noErr so that we process any partial
- // results.
- done = true;
- err = noErr;
- }
- if (err == noErr) {
- for (i = 0; i < foundItems; i++) {
- if (foundCatInfos[i].nodeFlags & kFSNodeIsDirectoryMask) {
- err = CopyFolder(&foundFSRefs[i], &foundCatInfos[i], &newDir, &foundNames[i], params);
- } else {
- err = CopyFile(&foundFSRefs[i], &foundCatInfos[i], &newDir, &foundNames[i], params);
- }
- if (err != noErr) {
- break;
- }
- }
- }
- } while (err == noErr && !done);
-
- junk = FSCloseIterator(iterator);
- MoreAssertQ(junk == noErr);
- }
- }
-
- // Reset the modification dates, except when copying to a drop folder
- // where this won't work.
-
- if (err == noErr && !params->copyingToDropFolder) {
- sourceCatInfo->createDate = originalCreateDate;
- err = FSSetCatalogInfo(&newDir, kFSCatInfoCreateDate
- | kFSCatInfoAttrMod
- | kFSCatInfoContentMod, sourceCatInfo);
- }
-
- // Clean up.
-
- if (foundCatInfos != nil) {
- MPFree(foundCatInfos);
- }
- if (foundFSRefs != nil) {
- MPFree(foundFSRefs);
- }
- if (foundNames != nil) {
- MPFree(foundNames);
- }
- params->currentDepth -= 1;
-
- return err;
- }
-
- enum {
- kDestInsideSourceErr = -1234
- };
-
- static OSStatus CheckForDestInsideSource(const FSRef *source, const FSRef *destDir)
- // Determines whether the destination directory is equal to the source
- // item, or whether it's nested inside the source item. Returns a
- // kDestInsideSourceErr if that's the case. We do this to prevent
- // endless recursion while copying.
- //
- // Frame Size: 288
- {
- OSStatus err;
- FSRef thisDir;
- Boolean done;
- FSCatalogInfo thisDirInfo;
-
- thisDir = *destDir;
- done = false;
- do {
- err = FSCompareFSRefs(source, &thisDir);
- if (err == noErr) {
- err = kDestInsideSourceErr;
- } else if (err == diffVolErr) {
- err = noErr;
- done = true;
- } else if (err == errFSRefsDifferent) {
-
- // This is somewhat tricky. We can ask for the parent of thisDir
- // by setting the parentRef parameter to FSGetCatalogInfo but, if
- // thisDir is the volume's FSRef, this will give us back junk.
- // So we also ask for the parent's dir ID to be returned in the
- // FSCatalogInfo record, and then check that against the node
- // ID of the root's parent (ie 1). If we match that, we've made
- // it to the top of the hierarchy without hitting source, so
- // we leave with no error.
-
- err = FSGetCatalogInfo(&thisDir, kFSCatInfoParentDirID, &thisDirInfo, nil, nil, &thisDir);
- if (err == noErr) {
- if (thisDirInfo.parentDirID == fsRtParID) {
- done = true;
- }
- }
- }
- } while ( err == noErr && ! done );
-
- return err;
- }
-
- enum {
- kPrivilegesMask = kioACUserNoSeeFolderMask | kioACUserNoSeeFilesMask | kioACUserNoMakeChangesMask,
- kDropFolderValue = kioACUserNoSeeFolderMask | kioACUserNoSeeFilesMask
- };
-
- static OSStatus CopyItemTopLevel(const FSRef *source, const FSRef *destDir,
- void *copyBuffer, ByteCount copyBufferSize)
- // This routine acts as the top level of the copy engine. It exists
- // to a) present a nicer API than the various recursive routines, and
- // b) minimise the local variables in the recursive routines.
- //
- // Frame Size: 752
- {
- OSStatus err;
- FSCatalogInfo tmpCatInfo;
- HFSUniStr255 destName;
- CopyParams params;
- FSVolumeRefNum destVRefNum;
-
- params.copyBuffer = copyBuffer;
- params.copyBufferSize = copyBufferSize;
- params.recursionLimit = kMaximumFolderDepth;
- params.currentDepth = 0;
-
- // Get info on the destination to determine whether it is a drop folder.
-
- err = FSGetCatalogInfo(destDir, kFSCatInfoUserPrivs, &tmpCatInfo, nil, nil, nil);
- if (err == noErr) {
- params.copyingToDropFolder = ((tmpCatInfo.userPrivileges & kPrivilegesMask) == kDropFolderValue);
-
- // Get info on the source to decide whether to kick off a file or
- // directory copy.
-
- err = FSGetCatalogInfo(source, kFSCatInfoSettableInfo, &tmpCatInfo, &destName, nil, nil);
- }
-
- // Get info on the destination to determine whether it is on a network volume.
-
- if (err == noErr) {
- err = VolumeContainingFSRef(destDir, &destVRefNum);
- }
- if (err == noErr) {
- err = GetFileSystemInfo(destVRefNum, nil, ¶ms.copyingToLocalVolume, nil);
- }
-
- if (err == noErr) {
-
- // Clear the "inited" bit so that the Finder positions the icon for us.
- // We do this here because we only want to clear the "inited" bit for the
- // top level item.
-
- ((FInfo *)(tmpCatInfo.finderInfo))->fdFlags &= ~kHasBeenInited;
-
- if (tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) {
- err = CheckForDestInsideSource(source, destDir);
- if (err == noErr) {
- err = CopyFolder(source, &tmpCatInfo, destDir, &destName, ¶ms);
- }
- } else {
- err = CopyFile(source, &tmpCatInfo, destDir, &destName, ¶ms);
- }
- }
- return err;
- }
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- MP Stuff -----
-
- static Boolean MPFileManagerAvailable(void)
- // Check to see whether we can use the File Manager from a
- // pre-emptive thread. If gestaltMPCallableAPIsAttr isn't
- // available in your "Gestalt.h", update to the very latest.
- {
- SInt32 response;
- return MPLibraryIsLoaded()
- && Gestalt(gestaltMPCallableAPIsAttr, &response) == noErr
- && (response & (1 << gestaltMPFileManager)) != 0;
- }
-
- // MP tasks take a single parameter when they start, but we need
- // to pass a bunch of parameters to the task, so we put them
- // in a structure.
-
- struct CopyTaskParams {
- const FSRef *source;
- const FSRef *destDir;
- void *copyBuffer;
- ByteCount copyBufferSize;
- };
- typedef struct CopyTaskParams CopyTaskParams;
-
- static OSStatus CopyFileTask(const CopyTaskParams *params)
- // This is the start routine of the MP task. It simply
- // unpacks the parameters from the CopyTaskParams structure
- // and calls the copy engine.
- //
- // Frame Size: 64
- {
- OSStatus err;
-
- err = CopyItemTopLevel(params->source, params->destDir, params->copyBuffer, params->copyBufferSize);
- return err;
- }
-
- static OSStatus CopyItemTopLevelMP(const FSRef *source, const FSRef *destDir,
- void *copyBuffer, ByteCount copyBufferSize)
- // This routine is a direct analogue of CopyFileTopLevel, except it
- // executes the copy in an MP task. The foreground process than
- // simply sits and waits for the task to complete, printing "."s
- // every now and again. This is not a particularly useful application
- // of multi-processing (-: but it's still pretty cool. For a start,
- // you can hold down the mouse button on a menu and the copy keeps
- // going.
- //
- // In a real application, this routine would probably be executed
- // by co-operative code that would drive the status window that
- // shows the user the status of the copy. Pressing the Cancel
- // button on the status window would set a variable in the
- // CopyTaskParams that the MP task would periodically check.
- // [Calling MPTerminateTask would probably be a bad idea because
- // there'd be no way for the task to dispose of the memory that
- // it's allocated during the copy.]
- {
- OSStatus err;
- OSStatus junk;
- MPQueueID terminationQueue;
- CopyTaskParams params;
- MPTaskID theCopyTask;
- Boolean done;
- OSStatus terminationErr;
- UInt32 timeLastPrinted;
- EventRecord event;
-
- terminationErr = noErr; // Just to make for more obvious debugging.
-
- // Fill out the CopyTaskParams.
-
- params.source = source;
- params.destDir = destDir;
- params.copyBuffer = copyBuffer;
- params.copyBufferSize = copyBufferSize;
-
- // Create the termination queue and the task itself.
-
- err = MPCreateQueue(&terminationQueue);
- if (err == noErr) {
- err = MPCreateTask( (TaskProc) CopyFileTask, ¶ms,
- kCopyFileTaskStackSize,
- terminationQueue,
- nil, nil, // no termination parameters
- 0, // no task options
- &theCopyTask);
-
- // Now wait for the copying task to complete, printing "."s
- // while we wait.
-
- if (err == noErr) {
- done = false;
- timeLastPrinted = TickCount();
- do {
- err = MPWaitOnQueue(terminationQueue,
- nil, nil, // not interested in these terminator parameters
- (void **) &terminationErr,
- kDurationImmediate);
- if (err == noErr) {
- done = true;
- err = terminationErr;
- } else if (err == kMPTimeoutErr) {
- if (TickCount() > timeLastPrinted + 10) {
- printf(".");
- fflush(stdout);
- timeLastPrinted = TickCount();
- }
- (void) WaitNextEvent(everyEvent, &event, 10, nil);
- (void) SIOUXHandleOneEvent(&event);
- err = noErr;
- }
- } while (err == noErr && ! done );
- }
-
- junk = MPDeleteQueue(terminationQueue);
- MoreAssertQ(junk == noErr);
- }
-
- return err;
- }
-
- /////////////////////////////////////////////////////////////////
- #pragma mark ----- Mainline -----
-
- static OSStatus ExtractSingleItem(const NavReplyRecord *reply, FSRef *item)
- // This item extracts a single FSRef from a NavReplyRecord.
- // Nav makes it really easy to support 'odoc', but a real pain
- // to support other things. *sigh*
- {
- OSStatus err;
- SInt32 itemCount;
- FSSpec fss;
- AEKeyword junkKeyword;
- DescType junkType;
- Size junkSize;
-
- MoreAssertQ((AECountItems(&reply->selection, &itemCount) == noErr) && (itemCount == 1));
-
- err = AEGetNthPtr(&reply->selection, 1, typeFSS, &junkKeyword, &junkType, &fss, sizeof(fss), &junkSize);
- if (err == noErr) {
- MoreAssertQ(junkType == typeFSS);
- MoreAssertQ(junkSize == sizeof(FSSpec));
-
- // We call FSMakeFSSpec because sometimes Nav is braindead
- // and gives us an invalid FSSpec (where the name is empty).
- // While FSpMakeFSRef seems to handle that (and the file system
- // engineers assure me that that will keep working (at least
- // on traditional Mac OS) because of the potential for breaking
- // existing applications), I'm still wary of doing this so
- // I regularise the FSSpec by feeding it through FSMakeFSSpec.
-
- if (fss.name[0] == 0) {
- err = FSMakeFSSpec(fss.vRefNum, fss.parID, fss.name, &fss);
- }
- if (err == noErr) {
- err = FSpMakeFSRef(&fss, item);
- }
- }
- return err;
- }
-
- static OSStatus ChooseSourceAndDest(FSRef *source, FSRef *dest)
- // Use Navigation Services (because I'm a good Apple employee)
- // to choose a source file and a destination folder,
- // and return them as FSRefs.
- {
- OSStatus err;
- OSStatus junk;
- NavDialogOptions options;
- NavReplyRecord reply;
-
- err = noErr;
- if (! NavServicesAvailable() ) {
- printf("Navigation Services is required.\n");
- err = -1;
- }
- if (err == noErr) {
- err = NavGetDefaultDialogOptions(&options);
- }
-
- // Get the source item.
-
- if (err == noErr) {
- options.dialogOptionFlags &= ~kNavAllowMultipleFiles;
- err = NavChooseObject(nil, &reply, &options, nil, nil, nil);
- }
- if (err == noErr) {
- err = ExtractSingleItem(&reply, source);
-
- junk = NavDisposeReply(&reply);
- MoreAssertQ(junk == noErr);
- }
-
- // Get the dest folder.
-
- if (err == noErr) {
- err = NavChooseFolder(nil, &reply, &options, nil, nil, nil);
- }
- if (err == noErr) {
- err = ExtractSingleItem(&reply, dest);
-
- junk = NavDisposeReply(&reply);
- MoreAssertQ(junk == noErr);
- }
-
- // Would be nice if I made the destination name unique at this point.
- // Too hard for the moment.
-
- return err;
- }
-
- void main(void)
- // The main line. Calls the copy engine to copy an item
- // named "Source" in the same directory as the application
- // to a folder named "Dest" in the same directory.
- {
- OSStatus err;
- FSRef source;
- FSVolumeRefNum sourceVol;
- FSRef dest;
- FSVolumeRefNum destVol;
- Ptr copyBuffer;
- Boolean runPreemptive;
- ByteCount bufferSize;
-
- printf("Hello Cruel World!\n");
- printf("MPFileCopy.c\n");
-
- // gState is just for debugging.
-
- printf("&gState = $%08x\n", &gState);
-
- // We flush stdout so that the SIOUX window comes up, which initialises
- // the toolbox for us, which we're going to need as soon as we
- // call ChooseSourceAndDest, which calls Navigation Services.
-
- fflush(stdout);
-
- #if !TARGET_API_MAC_CARBON
- gMyCompletionUPP = NewIOCompletionProc(MyCompletion);
- MoreAssert(gMyCompletionUPP != nil);
- #endif
-
- copyBuffer = nil;
-
- // Get source and dest objects using Nav.
-
- err = ChooseSourceAndDest(&source, &dest);
-
- // Decide whether we can run using MP tasks or not.
-
- if (err == noErr) {
- err = VolumeContainingFSRef(&source, &sourceVol);
- }
- if (err == noErr) {
- err = VolumeContainingFSRef(&dest, &destVol);
- }
- runPreemptive = ! kNeverUseMPTasks && MPFileManagerAvailable();
- if (err == noErr && runPreemptive) {
- err = IsVolumeTrulyAsync(sourceVol, &runPreemptive);
- if (err == noErr && runPreemptive && destVol != sourceVol) {
- err = IsVolumeTrulyAsync(destVol, &runPreemptive);
- }
- }
-
- // Allocate the copy buffer. We do this late because we
- // need to know the source and destination volumes to
- // judge the copy buffer size.
-
- if (err == noErr) {
- bufferSize = BufferSizeForThisVolume(sourceVol);
- if (destVol != sourceVol) {
- ByteCount tmp;
-
- tmp = BufferSizeForThisVolume(destVol);
- if (tmp < bufferSize) {
- bufferSize = tmp;
- }
- }
- copyBuffer = NewPtr(bufferSize);
- err = MemError();
- }
-
- // Under Mac OS 9.0, File Sharing's patches cause FSCreateFileUnicode to go
- // astray. We determine whether it's running now and then test the gFileSharingIsRunning
- // flag later to see whether we need to use the workaround.
-
- if (err == noErr) {
- err = IsFileSharingRunning(&gFileSharingIsRunning);
- }
-
- // The copy engine is going to set each item's creation date
- // to kMagicBusyCreationDate while it's copying the item.
- // But kMagicBusyCreationDate is an old-style 32-bit date/time,
- // while the HFS Plus APIs use the new 64-bit date/time. So
- // we have to call a happy UTC utilities routine to convert from
- // the local time kMagicBusyCreationDate to a UTCDateTime
- // gMagicBusyCreationDate, which the File Manager will store
- // on disk and which the Finder we read back using the old
- // APIs, whereupon the File Manager will convert it back
- // to local time (and hopefully get the kMagicBusyCreationDate
- // back!).
-
- if (err == noErr) {
- gMagicBusyCreationDate.highSeconds = 0;
- gMagicBusyCreationDate.fraction = 0;
- err = ConvertLocalTimeToUTC(kMagicBusyCreationDate, &gMagicBusyCreationDate.lowSeconds);
- }
-
- // Get the constant names for the resource and data fork, which
- // we're going to need inside the copy engine.
-
- if (err == noErr) {
- err = FSGetDataForkName(&gDataForkName);
- }
- if (err == noErr) {
- err = FSGetResourceForkName(&gRsrcForkName);
- }
-
- // Now call copy engine.
-
- if (err == noErr) {
- if ( runPreemptive ) {
- printf("Copying using pre-emptive thread.\n");
- fflush(stdout);
- err = CopyItemTopLevelMP(&source, &dest, copyBuffer, GetPtrSize(copyBuffer));
- } else {
- printf("Copying using co-operative main thread.\n");
- fflush(stdout);
- err = CopyItemTopLevel(&source, &dest, copyBuffer, GetPtrSize(copyBuffer));
- }
- }
-
- // Clean up.
-
- if (copyBuffer != nil) {
- DisposePtr(copyBuffer);
- MoreAssertQ(MemError() == noErr);
- }
-
- if (err == noErr) {
- printf("\nSuccess.\n");
- } else {
- printf("\nFailed with error %ld.\n", err);
- }
- printf("Done. Press command-Q to Quit.\n");
- }
-